package jmine.tec.report.impl.table.xls;

import java.util.Iterator;
import java.util.List;

import jmine.tec.report.api.RendererTarget;
import jmine.tec.report.api.ReportElement;
import jmine.tec.report.api.table.ReportCell;
import jmine.tec.report.api.table.ReportColumn;
import jmine.tec.report.api.table.ReportRow;
import jmine.tec.report.api.table.ReportTable;
import jmine.tec.report.impl.table.RendererParameterInterface;
import jmine.tec.report.xls.ReportElementXLSRenderer;

import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFCellStyle;
import org.apache.poi.hssf.usermodel.HSSFFont;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.hssf.util.HSSFColor;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.Font;
import org.apache.poi.ss.usermodel.Row;

/**
 * Renderer de tabelas de relatório em XLS
 * 
 * @author lundberg
 */
public class ReportTableXLSRenderer implements ReportElementXLSRenderer {

    private static final short TAMANHO_AJUSTE_COLUNAS = 400;

    private CellRendererXLSRegistry cellRendererRegistry;

    /**
     * {@inheritDoc}
     */
    public String getRendererType() {
        return ReportTable.RENDERER;
    }

    /**
     * {@inheritDoc}
     */
    public void render(HSSFWorkbook workbook, HSSFSheet sheet, ReportElement element) {
        this.doRender(workbook, sheet, (ReportTable<?>) element);
    }

    /**
     * Aplica o elemento de relatório ao sheet.
     * 
     * @param workbook workbook
     * @param <V> tipo de elemento do relatório
     * @param sheet sheet
     * @param element element
     */
    private <V> void doRender(HSSFWorkbook workbook, HSSFSheet sheet, ReportTable<V> element) {
        this.generateTableHeader(workbook, sheet, element.getColumns());

        this.generateTableContent(workbook, sheet, element);

        this.fixColumnsSize(sheet, element.getColumns().size());
    }

    /**
     * Popula os header da sheet de um documento do excel.
     * 
     * @param workbook workbook
     * @param <V> tipo de elemento das colunas
     * @param sheet sheet
     * @param columns lista de colunas da tabela
     */
    private <V> void generateTableHeader(HSSFWorkbook workbook, HSSFSheet sheet, List<ReportColumn<V>> columns) {
        HSSFCellStyle titleStyle = this.createTitleCellStyle(workbook);
        HSSFRow rowHeader = sheet.createRow(sheet.getLastRowNum());
        int index = 0;
        for (int col = 0; col < columns.size(); col++) {
            if (!this.createRPI(columns.get(col)).isHidden()) {
                this.renderCell(workbook, rowHeader, titleStyle, index++, columns.get(col).getHeader());
            }
        }
    }

    /**
     * Cria os conteúdos da tabela.
     * 
     * @param workbook workbook
     * @param <V> tipo de elemento das colunas
     * @param sheet sheet
     * @param element reportTable
     */
    private <V> void generateTableContent(HSSFWorkbook workbook, HSSFSheet sheet, ReportTable<V> element) {
        HSSFCellStyle contentStyle = this.createParamValueCellStyle(workbook);
        Iterator<ReportRow<V>> rowIterator = element.createRowIterator();
        while (rowIterator.hasNext()) {
            ReportRow<V> row = rowIterator.next();
            HSSFRow hssfRow = sheet.createRow(sheet.getLastRowNum() + 1);
            int index = 0;
            for (ReportColumn<V> column : element.getColumns()) {
                if (!this.createRPI(column).isHidden()) {
                    ReportCell cell = row.getCellMap().get(column.getId());
                    this.renderCell(workbook, hssfRow, contentStyle, index++, cell);
                }
            }
        }
    }

    /**
     * Aplica a célula à linha, na coluna dada.
     * 
     * @param workbook workbook
     * @param row row
     * @param style style
     * @param column column
     * @param cell cell
     */
    private void renderCell(HSSFWorkbook workbook, HSSFRow row, HSSFCellStyle style, int column, ReportCell cell) {
        ReportCellXLSRenderer renderer = this.cellRendererRegistry.getRenderer(cell.getRendererType());
        renderer.render(workbook, this.getCell(row, style, column), cell);
    }

    /**
     * Get a specific cell from a row. If the cell doesn't exist, then create it.
     * 
     * @param row The row that the cell is part of
     * @param style style
     * @param column The column index that the cell is in.
     * @return The cell indicated by the column.
     */
    private HSSFCell getCell(HSSFRow row, HSSFCellStyle style, int column) {
        HSSFCell cell = row.getCell(column, Row.RETURN_NULL_AND_BLANK);
        if (cell == null) {
            cell = row.createCell(column);
        }
        cell.setCellStyle(style);
        return cell;
    }

    /**
     * Ajusta o tamnho das colunas de uma sheet do arquivo.
     * 
     * @param sheet sheet
     * @param qtdeColunas quantidade de colunas da sheet.
     */
    private void fixColumnsSize(HSSFSheet sheet, int qtdeColunas) {
        for (int i = 0; i < qtdeColunas; i++) {
            sheet.autoSizeColumn((short) i);
            sheet.setColumnWidth(i, Math.min(sheet.getColumnWidth(i) + TAMANHO_AJUSTE_COLUNAS, MAX_COLUMN_WIDTH));
        }
    }

    /**
     * Cria um novo estilo de celula que deve ser utilizado nas celulas que possuem o valor de parametros.
     * 
     * @param workbook workbook
     * @return estilo de celula
     */
    private HSSFCellStyle createParamValueCellStyle(HSSFWorkbook workbook) {
        HSSFCellStyle style = workbook.createCellStyle();
        // Bordas
        style.setBorderBottom(CellStyle.BORDER_THIN);
        style.setBottomBorderColor(HSSFColor.BLACK.index);
        style.setBorderLeft(CellStyle.BORDER_THIN);
        style.setLeftBorderColor(HSSFColor.BLACK.index);
        style.setBorderRight(CellStyle.BORDER_THIN);
        style.setRightBorderColor(HSSFColor.BLACK.index);
        style.setBorderTop(CellStyle.BORDER_THIN);
        style.setTopBorderColor(HSSFColor.BLACK.index);

        // Cor de fundo
        style.setFillForegroundColor(HSSFColor.GREY_25_PERCENT.index);
        style.setFillPattern(CellStyle.SOLID_FOREGROUND);

        return style;
    }

    /**
     * Cria um novo estilo de celula que deve ser utilizado nas celulas de titulo (service name, service action).
     * 
     * @param workbook workbook
     * @return estilo de celula
     */
    private HSSFCellStyle createTitleCellStyle(HSSFWorkbook workbook) {
        HSSFCellStyle style = workbook.createCellStyle();

        // Bordas
        style.setBorderBottom(CellStyle.BORDER_THIN);
        style.setBottomBorderColor(HSSFColor.BLACK.index);
        style.setBorderLeft(CellStyle.BORDER_THIN);
        style.setLeftBorderColor(HSSFColor.BLACK.index);
        style.setBorderRight(CellStyle.BORDER_THIN);
        style.setRightBorderColor(HSSFColor.BLACK.index);
        style.setBorderTop(CellStyle.BORDER_THIN);
        style.setTopBorderColor(HSSFColor.BLACK.index);

        // Cor de fundo
        style.setFillForegroundColor(HSSFColor.GREY_80_PERCENT.index);
        style.setFillPattern(CellStyle.SOLID_FOREGROUND);

        // Fonte
        HSSFFont font = workbook.createFont();
        font.setBoldweight(Font.BOLDWEIGHT_BOLD);
        font.setColor(HSSFColor.WHITE.index);
        style.setFont(font);

        return style;
    }

    /**
     * @return the cellRendererRegistry
     */
    public CellRendererXLSRegistry getCellRendererRegistry() {
        return this.cellRendererRegistry;
    }

    /**
     * @param cellRendererRegistry the cellRendererRegistry to set
     */
    public void setCellRendererRegistry(CellRendererXLSRegistry cellRendererRegistry) {
        this.cellRendererRegistry = cellRendererRegistry;
    }

    /**
     * Cria um RendererParameterInterface para o target
     * 
     * @param target target
     * @return RendererParameterInterface
     */
    private RendererParameterInterface createRPI(RendererTarget target) {
        return new RendererParameterInterface(target);
    }

    /**
     * {@inheritDoc}
     */
    public boolean shouldRender() {
        return true;
    }
}
